#ifndef BSON_H_INCLUDED
#define BSON_H_INCLUDED

#include <e32std.h>
#include <e32base.h>

class RWriteStream;
class RReadStream;

namespace BSON
{

enum TElementType
{
  //element   ::=   
  EDouble               = 0x01, //     "\x01" e_name double  Floating point
  EUtf8String           = 0x02, // |   "\x02" e_name string  UTF-8 string
  EDocument             = 0x03, // |   "\x03" e_name document  Embedded document
  EArray                = 0x04, // |   "\x04" e_name document  Array
  EBinary               = 0x05, // |   "\x05" e_name binary  Binary data
//EUndefined            = 0x06, // |   "\x06" e_name   Undefined — Deprecated
  EObjectId             = 0x07, // |   "\x07" e_name (byte*12)   ObjectId
  EBoolean              = 0x08, // |   "\x08" e_name "\x00"  Boolean "false"
                                // |   "\x08" e_name "\x01"  Boolean "true"
  EUtcDateTime          = 0x09, // |   "\x09" e_name int64   UTC datetime
  ENull                 = 0x0A, // |   "\x0A" e_name   Null value
  ERegExp               = 0x0B, // |   "\x0B" e_name cstring cstring   Regular expression
//EDbPointer            = 0x0C, // |   "\x0C" e_name string (byte*12)  DBPointer — Deprecated
  EJavaScriptCode       = 0x0D, // |   "\x0D" e_name string  JavaScript code
  ESymbol               = 0x0E, // |   "\x0E" e_name string  Symbol
  EJavaScriptCodeWScope = 0x0F, // |   "\x0F" e_name code_w_s  JavaScript code w/ scope
  EInt32                = 0x10, // |   "\x10" e_name int32   32-bit Integer
//ETimeStamp            = 0x11, // |   "\x11" e_name int64   Timestamp
  EInt64                = 0x12, // |   "\x12" e_name int64   64-bit integer
  EMinKey               = 0xFF, // |   "\xFF" e_name   Min key
  EMaxKey               = 0x7F  // |   "\x7F" e_name   Max key
};

class CElement : public CBase
{
public:
  virtual ~CElement();

  virtual TElementType Type() const = 0;

  //Should return the length of the data in serialization format 
  virtual TInt DataLength() const = 0;
  virtual void DoSerializeL(::RWriteStream &aOut) const = 0;
  virtual void DoDeserializeL(::RReadStream &aIn) = 0;
  static TInt TypeSignatureLength();

//  friend class CDocument;
};

class CNull : public CElement
{
  CNull();
public:
  static CNull* NewL();
  static CNull* NewL(RReadStream& aIn);
  virtual TElementType Type() const;

  TInt DataLength() const;
  void DoSerializeL(::RWriteStream &aOut) const;
  void DoDeserializeL(RReadStream &aIn);
};

//namespace to hide implementation details from users.
namespace _
{

//template type used for providing valid storage type also for preventing instantiation of CValue with wrong TElementType value. 
template <int TElementTypeValue>
struct TypeEnumVerification {};

#define BSON_DECLARE_VALID_TYPE(ENUM_VAL, VALUE_TYPE, DATA_LENGTH) template <> struct TypeEnumVerification<ENUM_VAL> { typedef VALUE_TYPE TType; static const TInt KDataLength = DATA_LENGTH; }

//                      Enum value | Value type | Length in bits
BSON_DECLARE_VALID_TYPE(EDouble,       TReal64,   64);
BSON_DECLARE_VALID_TYPE(EBoolean,      bool,       8);
BSON_DECLARE_VALID_TYPE(EUtcDateTime,  TTime,     64);
BSON_DECLARE_VALID_TYPE(EInt32,        TInt32,    32);
BSON_DECLARE_VALID_TYPE(EInt64,        TInt64,    64);
#undef BSON_DECLARE_VALID_TYPE

void SerializeL(RWriteStream &aOut, const TReal64& aVal);
void SerializeL(RWriteStream &aOut, const bool& aVal);
void SerializeL(RWriteStream &aOut, const TTime& aVal);
void SerializeL(RWriteStream &aOut, const TInt32& aVal);
void SerializeL(RWriteStream &aOut, const TInt64& aVal);

void DeserializeL(RReadStream &aOut, TReal64& aVal);
void DeserializeL(RReadStream &aOut, bool& aVal);
void DeserializeL(RReadStream &aOut, TTime& aVal);
void DeserializeL(RReadStream &aOut, TInt32& aVal);
void DeserializeL(RReadStream &aOut, TInt64& aVal);

}

//template for representing simple T-types, as TBool, TInt32, TInt64, TTime, TReal64 as BSON values.
template <int TypeEnumValue>
class CValue : public CElement
{
public:
  typedef typename _::TypeEnumVerification<TypeEnumValue>::TType TValueType;

private:
  TValueType iValue;

  CValue(const TValueType& aValue)
    : iValue(aValue)
  {}
public:
  static CValue* NewL(const TValueType& aValue = TValueType())
  {
    return new (ELeave) CValue(aValue);
  }
  static CValue* NewL(RReadStream& aIn)
  {
    CValue* This = NewL();
    CleanupStack::PushL(This);
    This->DoDeserializeL(aIn);
    CleanupStack::Pop(This);
    return This;
  }

  TValueType& Value()
  {
    return iValue;
  }
  const TValueType& Value() const
  {
    return iValue;
  }
  void SetValue(const TValueType& aValue)
  {
    iValue = aValue;
  }
  TElementType Type() const
  {
    return static_cast<TElementType>(TypeEnumValue);
  }
protected:
  virtual void DoSerializeL(RWriteStream &aOut) const
  {
    return _::SerializeL(aOut, iValue);
  }
  virtual void DoDeserializeL(RReadStream &aOut)
  {
    return _::DeserializeL(aOut, iValue);
  }
  TInt DataLength() const
  {
    return _::TypeEnumVerification<TypeEnumValue>::KDataLength/8;
  }
};

typedef CValue<EDouble>      CDouble;
typedef CValue<EBoolean>     CBoolean;
typedef CValue<EUtcDateTime> CUtcDateTime;
typedef CValue<EInt32>       CInt32;      
typedef CValue<EInt64>       CInt64;    


class CString : public CElement
{
  RBuf iValue;
  const TElementType iType;

  CString(TElementType);

public:
  ~CString();

  //Valid TElementType values are EUtf8String, EJavaScriptCode, ESymbol, other values will raise KErrArgument.
  static CString* NewLC(HBufC* aValue, TElementType aType);
  static CString* NewL(HBufC* aValue, TElementType aType);
  static CString* NewLC(const TDesC& aValue, TElementType aType);
  static CString* NewL(const TDesC& aValue, TElementType aType);
  static CString* NewL(RReadStream& aIn, TElementType);

  TPtrC Value() const;
  void SetValueL(const TDesC& aNewValue);
  void SetValue(HBufC* aNewValue);
  TElementType Type() const;

protected:
  virtual void DoSerializeL(RWriteStream &aOut) const;
  virtual void DoDeserializeL(RReadStream &aIn);
  TInt DataLength() const;
};


class CBinary : public CElement
{
public:
  enum TSubType
  {
    EGeneric     = 0x0,
    EFunction    = 0x01,
    EOldBinary   = 0x02,
    EUUID        = 0x03,
    EMD5         = 0x05,
    EUserDefined = 0x80
  };

  static CBinary* NewL(const TDesC8& aData, TSubType aSubType);
  static CBinary* NewL(HBufC8* aData, TSubType aSubType);//ownership is taken
  static CBinary* NewL(RReadStream& aIn);
  ~CBinary();

  TPtrC8 Value() const;
  void SetValueL(const TDesC8& aData);
  void SetValue(HBufC8* aData);//ownership is taken
  TSubType SubType() const;

  //from CElement
  TElementType Type() const;
private:
  virtual void DoSerializeL(RWriteStream &aOut) const;
  virtual void DoDeserializeL(RReadStream &aIn);
  TInt DataLength() const;

  TSubType iSubType;
  RBuf8 iValue;

  CBinary(TSubType aSubType);
};


class CArray : public CElement
{
public:
  ~CArray();
  static CArray* NewLC();
  static CArray* NewL();
  static CArray* NewL(RReadStream& aIn);

  void Reset();
  TInt Count() const;
  CElement& operator[](TInt aIndex);
  const CElement& operator[](TInt aIndex) const;

  void AppendL(CElement* aValue);

  void AppendL();//Appends null value.
  void AppendL(bool);
  void AppendL(TInt);
  void AppendL(TInt32);
  void AppendL(const TInt64&);
  void AppendL(const TTime&);
  void AppendL(const TReal64&);

  //see CString for valid TElementType values
  void AppendL(const TDesC& aString, TElementType = EUtf8String);
  void AppendL(HBufC* aString, TElementType = EUtf8String);//ownership is taken

  void AppendL(const TDesC8& aBinary, CBinary::TSubType);
  void AppendL(HBufC8* aBinary, CBinary::TSubType);//ownership is taken


private:
  //from CElement
  virtual TElementType Type() const;
  virtual TInt DataLength() const;
  virtual void DoSerializeL(::RWriteStream &aOut) const;
  virtual void DoDeserializeL(RReadStream &aIn);

  CArray();
  void ConstructL();
  void SafeAppendL(CElement* aElement);

  TInt ComputeIndexNameLength(TInt aIndex) const;
  TPtrC NameForIndex(TInt aIndex) const;

  RPointerArray<CElement> iElements;
  mutable RBuf iNameGenerator;
};

class CDocument : public CElement
{
  TBool iIsEmbeded;
public:
  ~CDocument();
  static CDocument* NewLC();
  static CDocument* NewL();
  static CDocument* NewL(RReadStream& aIn);

  class CValuePair
  {
    friend class CDocument;
    RBuf iName;
    CElement* iElement;
  public:
    static CValuePair* NewL(const TDesC& aName, CElement* aElement);
    static CValuePair* NewL(HBufC*, CElement* aElement);
    ~CValuePair();

    TPtrC Name() const;
    void SetNameL(const TDesC& aName);
    CElement& Element();
    const CElement& Element() const;
  };

  void Reset();
  TInt Count() const;
  CValuePair& operator[](TInt aIndex);
  const CValuePair& operator[](TInt aIndex) const;

  CElement* GetByName(const TDesC& aName);
  const CElement* GetByName(const TDesC& aName) const;

  void AppendL(const TDesC& aName, CElement* aValue);

  void AppendL(const TDesC& aName);//Appends null value.
  void AppendL(const TDesC& aName, bool);
  void AppendL(const TDesC& aName, TInt);
  void AppendL(const TDesC& aName, TInt32);
  void AppendL(const TDesC& aName, const TInt64&);
  void AppendL(const TDesC& aName, const TTime&);
  void AppendL(const TDesC& aName, const TReal64&);

  //see CString for valid TElementType values
  void AppendL(const TDesC& aName, const TDesC& aString, TElementType = EUtf8String);
  void AppendL(const TDesC& aName, HBufC* aString, TElementType = EUtf8String);//ownership is taken

  void AppendL(const TDesC& aName, const TDesC8& aBinary, CBinary::TSubType);
  void AppendL(const TDesC& aName, HBufC8* aBinary, CBinary::TSubType);//ownership is taken

  TBool IsEmbeded() const;
  void  SetEmbeded(TBool);

  HBufC8* SerializeLC() const;
  HBufC8* SerializeL() const;
  void SerializeL(RWriteStream &aOut) const;

  void ParseL(const TDesC8&);
  void ParseL(RReadStream& aIn);

private:
  //from CElement
  virtual TElementType Type() const;
  virtual TInt DataLength() const;
  virtual void DoSerializeL(::RWriteStream &aOut) const;
  virtual void DoDeserializeL(RReadStream &aIn);

private:
  CDocument();
  void SafeAppendL(const TDesC& aName, CElement* aElement);
  void AppendL(HBufC* aName, CElement* aElement);
  void DoAppendL(const TDesC& aName, CElement* aElement);

  RPointerArray<CValuePair> iData;
};


CDocument* ParseL(const TDesC8& aData);

}

#endif // BSON_H
